﻿using IOP.Net;
using IOP.Pulsar.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

namespace IOP.Pulsar.ProductLineClients.TCP
{
    /// <summary>
    /// 管线TCP客户端
    /// </summary>
    public class ProductLineTCPClient<TContext> : IProductLineClient<TContext>
        where TContext : class
    {
        /// <summary>
        /// 连接的嵌套字
        /// </summary>
        public SocketMonitor ConnectedSocket { get; private set; }
        /// <summary>
        /// 生产线入口
        /// </summary>
        public IProductLineEntry<TContext> ProductLineEntry { get; protected set; }
        /// <summary>
        /// 客户端配置
        /// </summary>
        public IClientOptions ClientOption { get; private set; }

        /// <summary>
        /// 当客户端被关闭时
        /// </summary>
        public event Action<ProductLineTCPClient<TContext>> OnClosed = null;

        private volatile bool _IsConnected;
        /// <summary>
        /// 是否连接
        /// </summary>
        public bool IsConnected
        {
            get => _IsConnected;
            protected set => _IsConnected = value;
        }
        private bool _IsDispose;
        /// <summary>
        /// 是否释放资源
        /// </summary>
        public bool IsDispose 
        {
            get { return Volatile.Read(ref _IsDispose); }
            protected set { Volatile.Write(ref _IsDispose, value); }
        }
        /// <summary>
        /// 日志
        /// </summary>
        public readonly ILogger<ProductLineTCPClient<TContext>> Logger;

        /// <summary>
        /// 管线
        /// </summary>
        private readonly Pipe _Pipe = new Pipe();
        /// <summary>
        /// 管线构建者
        /// </summary>
        protected IProductLineBuilder<TContext> _ProductLineBuilder;
        /// <summary>
        /// 服务提供者
        /// </summary>
        protected IServiceProvider _ServicesProvider;
        private const int MinimumBufferSize = 1500;
        private AutoResetEvent _TaskController = new AutoResetEvent(false);

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="services">服务提供者</param>
        /// <param name="buildAction">管线构建委托</param>
        public ProductLineTCPClient(IServiceProvider services, Action<IProductLineBuilder<TContext>> buildAction)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (buildAction == null) throw new ArgumentNullException(nameof(buildAction));
            _ServicesProvider = services;
            IProductLineEntry<TContext> entry = _ServicesProvider.GetRequiredService<IProductLineEntry<TContext>>();
            ProductLineEntry = entry;
            _ProductLineBuilder = new ProductLineBuilder<TContext>(_ServicesProvider);
            buildAction?.Invoke(_ProductLineBuilder);
            var productionLine = _ProductLineBuilder.Build();
            ProductLineEntry.ProductLine = productionLine;
            Logger = services.GetRequiredService<ILoggerFactory>().CreateLogger<ProductLineTCPClient<TContext>>();
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="services">服务提供者</param>
        /// <param name="buildAction">管线构建委托</param>
        public ProductLineTCPClient(Action<IServiceCollection> services, Action<IProductLineBuilder<TContext>> buildAction)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (buildAction == null) throw new ArgumentNullException(nameof(buildAction));
            IServiceCollection collections = new ServiceCollection();
            services?.Invoke(collections);
            _ServicesProvider = collections.BuildServiceProvider();
            IProductLineEntry<TContext> entry = _ServicesProvider.GetRequiredService<IProductLineEntry<TContext>>();
            ProductLineEntry = entry;
            _ProductLineBuilder = new ProductLineBuilder<TContext>(_ServicesProvider);
            buildAction?.Invoke(_ProductLineBuilder);
            var productionLine = _ProductLineBuilder.Build();
            ProductLineEntry.ProductLine = productionLine;
            Logger = _ServicesProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ProductLineTCPClient<TContext>>();
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="services"></param>
        /// <param name="buildAction"></param>
        public ProductLineTCPClient(IServiceCollection services, Action<IProductLineBuilder<TContext>> buildAction)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
            if (buildAction == null) throw new ArgumentNullException(nameof(buildAction));
            _ServicesProvider = services.BuildServiceProvider();
            IProductLineEntry<TContext> entry = _ServicesProvider.GetRequiredService<IProductLineEntry<TContext>>();
            ProductLineEntry = entry;
            _ProductLineBuilder = new ProductLineBuilder<TContext>(_ServicesProvider);
            buildAction?.Invoke(_ProductLineBuilder);
            var productionLine = _ProductLineBuilder.Build();
            ProductLineEntry.ProductLine = productionLine;
            Logger = _ServicesProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ProductLineTCPClient<TContext>>();
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="entry">管线入口</param>
        /// <param name="buildAction">管线构建委托</param>
        public ProductLineTCPClient(IProductLineEntry<TContext> entry, Action<IProductLineBuilder<TContext>> buildAction)
        {
            if (entry == null) throw new ArgumentNullException(nameof(entry));
            if (buildAction == null) throw new ArgumentNullException(nameof(buildAction));
            IServiceCollection collections = new ServiceCollection();
            collections.AddLogging();
            collections.AddTransient(factory => entry);
            _ServicesProvider = collections.BuildServiceProvider();
            ProductLineEntry = entry;
            _ProductLineBuilder = new ProductLineBuilder<TContext>(_ServicesProvider);
            buildAction?.Invoke(_ProductLineBuilder);
            var productionLine = _ProductLineBuilder.Build();
            ProductLineEntry.ProductLine = productionLine;
            Logger = _ServicesProvider.GetRequiredService<ILoggerFactory>().CreateLogger<ProductLineTCPClient<TContext>>();
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="entry"></param>
        /// <param name="builder"></param>
        /// <param name="buildAction"></param>
        public ProductLineTCPClient(IProductLineEntry<TContext> entry, IProductLineBuilder<TContext> builder, Action<IProductLineBuilder<TContext>> buildAction = null)
        {
            if (entry == null) throw new ArgumentNullException(nameof(entry));
            if (builder == null) throw new ArgumentNullException(nameof(builder));
            ProductLineEntry = entry;
            buildAction?.Invoke(builder);
            var productionLine = _ProductLineBuilder.Build();
            ProductLineEntry.ProductLine = productionLine;
        }
        /// <summary>
        /// 构造函数
        /// </summary>
        protected ProductLineTCPClient() { }

        /// <summary>
        /// 执行连接
        /// </summary>
        /// <param name="option"></param>
        public virtual async Task ConnectAsync(IClientOptions option)
        {
            ClientOption = option;
            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            if (ConnectedSocket == null) ConnectedSocket = new SocketMonitor(socket);
            else ConnectedSocket.ConnectedSocket = socket;
            var ip = await Dns.GetHostAddressesAsync(option.Address);
            ip = ip.Where(x => x.AddressFamily == AddressFamily.InterNetwork).ToArray();
            if (!ip.Any()) throw new ArgumentException($"Cannot get ipaddress from {option.Address}");
            var point = new IPEndPoint(ip[0], option.Port);
            ConnectedSocket.ConnectedSocket.Connect(point);
            _IsConnected = true;
            Logger?.LogInformation($"Client success connected to {point.ToString()}");
            ProcessAsync(ConnectedSocket);
        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="data"></param>
        public virtual void Send(Span<byte> data)
        {
            ConnectedSocket.ConnectedSocket.Send(data);
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public virtual void Disconnect()
        {
            ConnectedSocket.ConnectedSocket.Shutdown(SocketShutdown.Both);
            ConnectedSocket.ConnectedSocket.Disconnect(false);
            ConnectedSocket.ConnectedSocket.Close();
            while (_IsConnected) 
            { 
                _TaskController.WaitOne(8000);
                if (_IsConnected) throw new TimeoutException("Disconnected socket failed, Disconnect Timeout");
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public virtual void Dispose()
        {
            if (IsConnected) Disconnect();
            ConnectedSocket.Dispose();
            ProductLineEntry.Dispose();
            IsConnected = false;
            IsDispose = true;
        }

        /// <summary>
        /// 消息处理函数
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        private void ProcessAsync(SocketMonitor socket)
        {
            _ = FillPipeAsync(socket, _Pipe.Writer);
            _ = ReadPipeAsync(socket, _Pipe.Reader);
        }
        /// <summary>
        /// 填充管线
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="writer"></param>
        /// <returns></returns>
        private async ValueTask FillPipeAsync(SocketMonitor socket, PipeWriter writer)
        {
            while (true)
            {
                try
                {
                    Memory<byte> memory = writer.GetMemory(MinimumBufferSize);
                    int bytesRead = await socket.ConnectedSocket.ReceiveAsync(memory, SocketFlags.None);
                    if (bytesRead == 0) break;
                    writer.Advance(bytesRead);
                }
                catch (Exception e) 
                {
                    Logger?.LogError(e.Message + "\r\n" + e.StackTrace);
                    break;
                }
                FlushResult result = await writer.FlushAsync();
                if (result.IsCompleted) break;
            }
            writer.Complete();
        }
        /// <summary>
        /// 消费管线数据
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="reader"></param>
        /// <returns></returns>
        private async ValueTask ReadPipeAsync(SocketMonitor socket, PipeReader reader)
        {
            try
            {
                while (true)
                {
                    ReadResult result = await reader.ReadAsync();
                    socket.UpdateLastMessageTime(DateTime.Now);
                    ReadOnlySequence<byte> buffer = result.Buffer;
                    ProductLineEntry.EntryHandle(ref buffer, socket);
                    reader.AdvanceTo(buffer.Start, buffer.End);
                    if (result.IsCompleted) break;
                }
            }
            catch (Exception e)
            {
                Logger?.LogError(e.Message + "\r\n" + e.StackTrace);
            }
            finally
            {
                reader.Complete();
                _IsConnected = false;
                _Pipe.Reset();
                _TaskController.Set();
                OnClosed?.Invoke(this);
                Logger?.LogInformation($"Client disconnected");
            }
        }
    }
}
